//
//   Copyright 2011 Gynvael Coldwind & Mateusz "j00ru" Jurczyk
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//
// GameJam-ZRH, 2011, Vexillium Team (j00ru+gynvael)
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <set>
#ifdef __APPLE__
#  include <OpenGL/gl.h>
#  include <OpenGL/glu.h>
#else
#  include <GL/gl.h>
#  include <GL/glu.h>
#endif
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_mixer.h>
#ifdef _WIN32
 #include <windows.h>
#endif
#include <stdint.h>
#include <math.h>

using namespace std;

#ifdef _WIN32
// SDL_main does nasty things to my stdout.
#  undef main
#endif

#include "j00ru.h"
#include "gynvael.h"
#include "global.h"
#include "record.h"
#include "music.h"

// globals
int screen_width;
int screen_height;
bool keys[SDLK_LAST];
uint32_t last_time;
float ratio;

unsigned int textures[100];

static bool initSDL(bool fullscreen, int width, int height);
static bool initOpenGL();
static void scene();
static bool events();

int mouse_x, mouse_y;

// 
// Current level descriptor
//
GAME_STATE g_GameState;
uint32_t g_Ticks;
uint32_t g_PendingMs;

RECORDER_STRUCT g_Recorder;

int g_ObjectParams[PARAMETRIZED_OBJECT_COUNT][3];
vector<OBJECT_STRUCT*> g_Objects;

int g_GameMode;
int g_ModeStartTime;
int g_MenuItem;


void game_SwitchMode(int mode)
{
  g_GameMode      = mode;
  g_ModeStartTime = SDL_GetTicks();

  // Perform initialization specific to a given mode
  switch(mode)
  {
    case GAME_MODE_LOSE:
      music_playSound(SND_FAILURE);
      break;

    case GAME_MODE_GAME_FINISHED:
      g_GameState.levelNumber = 0;

    case GAME_MODE_LVL_FINISHED:
      music_playSound(SND_SUCCESS);
      break;

    case GAME_MODE_MENU:
      g_MenuItem = 0;

      // Initialize background music, if required
      if(!music_MusicExists("bg"))
      {
        music_AddMusic("sfx/bg.ogg", "bg");
        music_playMusic("bg");
      }

      break;

    case GAME_MODE_ABOUT:
      g_AboutPage = 0;
      break;
    
    case GAME_MODE_GAMEPLAY:
    
      music_playMusic("bg");

      //
      // Most likely, the level will already be loaded by now
      // (thanks to GAME_MODE_BEFORE_LEVEL). If it is not, 
      // try to do that now.
      //
      
      if(g_GameState.currentLevel == NULL)
      {
        g_GameState.currentLevel = game_LoadLevel();

        if(g_GameState.currentLevel == NULL)
        {
          game_SwitchMode(GAME_MODE_GAME_FINISHED);
          return;
        }

        // Initialize player data in the game state structure
        player_InitGameState();
      }

      // Initialize recorders
      rec_Clean(&g_Recorder);
      rec_Reset(&g_Recorder);

      // Make a snapshot of the current state
      rec_MakeSnapshot(&g_Recorder);

      break;

    case GAME_MODE_BEFORE_LEVEL:
      {
        g_GameState.currentLevel = game_LoadLevel();
        if(g_GameState.currentLevel == NULL)
        {
          game_SwitchMode(GAME_MODE_GAME_FINISHED);
          return;
        }

        // Initialize player data in the game state structure
        player_InitGameState();

        // Clean all remaining objects
        g_Objects.clear();
      }
      break;
      
  }
}

void game_ExpireMode()
{
  switch(g_GameMode)
  {
    case GAME_MODE_INTRO:
      if(SDL_GetTicks() - g_ModeStartTime > 3000)
        game_SwitchMode(GAME_MODE_MENU);
      break;      

    case GAME_MODE_LOSE:
      if(SDL_GetTicks() - g_ModeStartTime > 3000)
        game_SwitchMode(GAME_MODE_BEFORE_LEVEL);
      break;

    case GAME_MODE_LVL_FINISHED:
      if(SDL_GetTicks() - g_ModeStartTime > 3000)
        game_SwitchMode(GAME_MODE_BEFORE_LEVEL);
      break;

    case GAME_MODE_GAME_FINISHED:
      if(SDL_GetTicks() - g_ModeStartTime > 3000)
        game_SwitchMode(GAME_MODE_MENU);
      break;

    case GAME_MODE_BEFORE_LEVEL:
      if(SDL_GetTicks() - g_ModeStartTime > 8000)
        game_SwitchMode(GAME_MODE_GAMEPLAY);
      break;

    // TODO: Add the expiration of specific modes,
    // which are not supposed to last longer than for a given time period.

    default:
      break;
  }
}

void game_HandleKeyboard()
{
  switch(g_GameMode)
  {
    case GAME_MODE_INTRO:
      if(keys[SDLK_ESCAPE] || keys[SDLK_RETURN])
      {
        keys[SDLK_ESCAPE] = false;
        keys[SDLK_RETURN] = false;
        game_SwitchMode(GAME_MODE_MENU);
      }
      break;

    case GAME_MODE_MENU:
      {
        if(keys[SDLK_UP])
        {
          keys[SDLK_UP] = false;

          g_MenuItem--;
          if(g_MenuItem == -1)
            g_MenuItem = 2;

          music_playSound(SND_MENU_CHANGE);
        }
        else if(keys[SDLK_DOWN])
        {
          keys[SDLK_DOWN] = false;
          g_MenuItem++;
          if(g_MenuItem == 3)
            g_MenuItem = 0;

          music_playSound(SND_MENU_CHANGE);
        }
        else if(keys[SDLK_RETURN])
        {
          keys[SDLK_RETURN] = false;

          music_playSound(SND_MENU_SELECT);

          if(g_MenuItem == MENU_ITEM_PLAY)
            game_SwitchMode(GAME_MODE_BEFORE_LEVEL);
          else if(g_MenuItem == MENU_ITEM_ABOUT)
            game_SwitchMode(GAME_MODE_ABOUT);
          else if(g_MenuItem == MENU_ITEM_EXIT)
            game_SwitchMode(GAME_MODE_EXIT);
        }
      }
      break;

    case GAME_MODE_ABOUT:
      if(keys[SDLK_ESCAPE])
        game_SwitchMode(GAME_MODE_MENU);

      if(keys[SDLK_UP] || keys[SDLK_LEFT])
      {
        keys[SDLK_UP] = false;
        keys[SDLK_LEFT] = false;
        if(g_AboutPage > 0)
          g_AboutPage--;
      }

      if(keys[SDLK_DOWN] || keys[SDLK_RIGHT])
      {
        keys[SDLK_DOWN] = false;
        keys[SDLK_RIGHT] = false;
        g_AboutPage++; // this is limited in gynvael.cpp
      }



      break;

    case GAME_MODE_GAMEPLAY:
      if(keys[SDLK_ESCAPE])
        game_SwitchMode(GAME_MODE_MENU);
      break;

    case GAME_MODE_LOSE:
      if(keys[SDLK_RETURN] || keys[SDLK_ESCAPE])
      {
        keys[SDLK_RETURN] = false;
        keys[SDLK_ESCAPE] = false;
        game_SwitchMode(GAME_MODE_MENU);
      }
      break;

    case GAME_MODE_LVL_FINISHED:
      if(keys[SDLK_RETURN] || keys[SDLK_ESCAPE])
      {
        keys[SDLK_RETURN] = false;
        keys[SDLK_ESCAPE] = false;
        game_SwitchMode(GAME_MODE_BEFORE_LEVEL);
      }
      break;

    case GAME_MODE_BEFORE_LEVEL:
      if(keys[SDLK_RETURN] || keys[SDLK_ESCAPE])
      {
        keys[SDLK_RETURN] = false;
        keys[SDLK_ESCAPE] = false;
        game_SwitchMode(GAME_MODE_GAMEPLAY);
      }
      break;

    case GAME_MODE_GAME_FINISHED:
      if(keys[SDLK_ESCAPE] || keys[SDLK_RETURN])
      {
        keys[SDLK_RETURN] = false;
        keys[SDLK_ESCAPE] = false;
        game_SwitchMode(GAME_MODE_MENU);
      }
      break;
  }
}


// main
int 
main(int argc, char **argv)
{
  if(argc == 3) {
    if(strcmp(argv[1], "--level") == 0) 
      g_GameState.levelNumber = atoi(argv[2]);
  }

  if(!initSDL(false, 800, 600))    return 1;
  if(!initOpenGL()) return 2;
  music_Init();

  gynvael_LoadTextures();

  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  ratio = 0.0;
  last_time = 0xffffffff;

  // Set the current mode to "menu mode"
  game_SwitchMode(GAME_MODE_INTRO);

  while(1)
  {
    uint32_t now_time = SDL_GetTicks();
    uint32_t delta    = now_time - last_time;

    if(last_time != 0xffffffff)
      ratio = (float)delta / 1000.0f;
    last_time = now_time;

    if(g_GameMode == GAME_MODE_GAMEPLAY)
    { 
      // update ticks
      g_Ticks += ((delta + g_PendingMs) / TICK_INTERVAL);
      g_PendingMs = (delta + g_PendingMs) % TICK_INTERVAL;

      // Decrease the amount of time left. If the time is over, bail out
      g_GameState.timeLeft -= ratio;
      if(g_GameState.timeLeft <= 0.0f)
      {
        if(g_GameState.activePlayer < g_GameState.currentLevel->playerCount - 1)
        {
          // Emulate a SPACE press in order to trigger player switch
          keys[SDLK_SPACE] = true;
        }
        else
        {
          game_SwitchMode(GAME_MODE_LOSE);
        }
      }

      // update objects
      gynvael_UpdateObjects();

      // update player position
      player_UpdatePosition();
      player_HandleSpells();

      // Calc mouse stuff.
      gynvael_GetMouse(&mmx, &mmy);
      gynvael_RecalcTarget(mmx, mmy);

      // update objects
      gynvael_UpdateObjects();

      // record all objects
      rec_InsertCurrentPlayer(&g_Recorder);
      rec_PlayAvailableObjects(&g_Recorder);

      // handle special locations
      player_HandleSpecialLocations();
    }

    game_HandleKeyboard();
    game_ExpireMode();

    if(g_GameMode == GAME_MODE_EXIT)
      break;

    if(!events())
      break;

    scene();
  }

  Mix_CloseAudio();
  SDL_Quit();

  return 0;
}

// scene
void
static scene()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();

  gynvael_MakeAScene();

  SDL_GL_SwapBuffers(); 
}


// events
static bool
events()
{
  SDL_Event ev;
  memset(&ev, 0, sizeof(ev));
  while(SDL_PollEvent(&ev))
  {
    switch( ev.type )
    { 
      case SDL_MOUSEMOTION:
        mouse_x = ev.motion.x;
        mouse_y = ev.motion.y;
        break;

      case SDL_MOUSEBUTTONDOWN:
        mouse_x = ev.button.x;
        mouse_y = ev.button.y;
        if(ev.button.button == 1) lmb = true;
        if(ev.button.button == 3) rmb = true;
        break;        

      case SDL_MOUSEBUTTONUP:
        mouse_x = ev.button.x;
        mouse_y = ev.button.y;        
        if(ev.button.button == 1) lmb = false;
        if(ev.button.button == 3) rmb = false;
        break;                

 

      case SDL_KEYUP:
        keys[ev.key.keysym.sym] = false;	
	      break;
      
      case SDL_KEYDOWN:
      
        keys[ev.key.keysym.sym] = true;
        	break;	
	
      case SDL_QUIT:
        return false;      
    }
  }

  return true;
}

// initSDL
static bool
initSDL(bool fullscreen, int width, int height)
{
  int screen_bpp;
  int screen_flag = SDL_OPENGL;
  if(fullscreen) screen_flag |= SDL_FULLSCREEN;

  screen_width = width;
  screen_height = height;
 
  const SDL_VideoInfo *info;
  SDL_Surface *surface;  

  if( SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE | SDL_INIT_AUDIO) < 0 )
    return false;
 
  info = SDL_GetVideoInfo( );
  if(info == NULL)
    goto err;

  screen_bpp = info->vfmt->BitsPerPixel;
  SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
  SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
  SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

  surface = SDL_SetVideoMode(
            screen_width, screen_height, 
            screen_bpp, screen_flag);

  if(surface == NULL)
    goto err;

  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

  SDL_ShowCursor(SDL_DISABLE);

  return true;
  
err:
  Mix_CloseAudio();
  SDL_Quit();
  return false;
}

// initOpenGL
static bool
initOpenGL()
{
  float ratio;
  ratio = (float)screen_width / (float)screen_height;
  
  glShadeModel(GL_SMOOTH);  
  glClearColor(0, 0, 0, 0);  
  glViewport(0, 0, screen_width, screen_height);  
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity( );
  gluPerspective( 60.0, ratio, 1.0, 1024.0 );
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
  glDisable(GL_DEPTH_TEST); // uncomment this if going 2D
  return true;
}

unsigned int 
make_texture(const char* file_name)
{
  // function to load texture
  GLuint texture;
  SDL_Surface *img;

  img = IMG_Load(file_name);
  if(!img)
  {
    fprintf(stderr, "File %s not found", file_name);
    return 0;
  }

  glGenTextures(1, &texture);
  glBindTexture(GL_TEXTURE_2D, texture);
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

#ifdef __APPLE__
  // For some reason apple SDL_image switches textures to BGR
  // Convert them back to something sane
  
  // Convert to 32 bits.
  SDL_PixelFormat fmt = {
    NULL, 32, 4, 
    0, 0, 0, 0,
    0, 8, 16, 24,
    0xff, 0xff00, 0xff0000, 0xff000000,
    0,
    0xff
  };
  {
    SDL_Surface *nimg = SDL_ConvertSurface(img, &fmt, SDL_SWSURFACE);
    SDL_FreeSurface(img);
    img = nimg;
  }
#endif


  if(img->format->BitsPerPixel == 24)
  {
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img->w, 
	img->h, 0, GL_RGB, GL_UNSIGNED_BYTE, img->pixels);
    printf("File %s is 24 bits (as 24)\n", file_name);
  }
  else if (img->format->BitsPerPixel == 32)
  {
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img->w, 
	img->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, img->pixels);
    printf("File %s is 32 bits\n", file_name);
  }
  else
  {
    fprintf(stderr, "File %s has unsupported BPP (%i)\n",
        file_name, img->format->BitsPerPixel);
    return 0;
  }

  return texture;
}

